package com.vivz.guess.what.vo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import com.vivz.guess.what.type.Position;

public final class ProbabilityMatrix {

	/**
	 * The map of all characters to their probable positions.
	 */
	private final Map<Character, Set<Position>> map;
	/**
	 * The set of definite outsiders.
	 */
	private final Set<Character> doSet;
	/**
	 * The map of definite insiders to their probable positions.
	 */
	private final Map<Character, Set<Position>> diMap;
	/**
	 * The number of characters the guess and keyword should have.
	 */
	private final Integer degree;

	public ProbabilityMatrix(final int degree) {
		map = new TreeMap<Character, Set<Position>>();
		for (char ch = 'A'; ch <= 'Z'; ch++) {
			final List<Position> list = new ArrayList<Position>();
			for (int i = 1; i <= degree; i++) {
				list.add(Position.getInstance(i));
			}
			final Set<Position> positionSet = new TreeSet<Position>(list);
			map.put(Character.toUpperCase(ch), positionSet);
		}
		doSet = new TreeSet<Character>();
		diMap = new TreeMap<Character, Set<Position>>();
		this.degree = degree;
	}

	public Set<Character> getDefiniteOutsiders() {
		return Collections.unmodifiableSet(doSet);
	}

	public Map<Character, Set<Position>> getDefiniteInsiders() {
		return Collections.unmodifiableMap(diMap);
	}

	public Set<Character> getProbableInsiders() {
		return Collections.unmodifiableSet(map.keySet());
	}

	public void markImpossibleCharacter(final Character c) {
		map.remove(Character.toUpperCase(c));
		doSet.add(Character.toUpperCase(c));
	}

	public void markOnlyPossiblePosition(final Character c, final Position p) {
		if (!doSet.contains(Character.toUpperCase(c))) {
			final Set<Position> set = new HashSet<Position>();
			set.add(p);
			map.put(Character.toUpperCase(c), set);

			if (p.isConfirmed()) {
				final Set<Position> singleton = new HashSet<Position>();
				singleton.add(p);
				diMap.put(Character.toUpperCase(c), singleton);
			}

		}

	}

	/**
	 * This method removes the position p as a possibility for character c.
	 * 
	 * @param c
	 * @param p
	 * @param confirmed
	 */
	public void markOnlyImpossiblePosition(final Character c, final Position p, final boolean confirmed) {
		if (!doSet.contains(Character.toUpperCase(c))) {
			map.get(Character.toUpperCase(c)).remove(p);
			removeEmptyPositionSets();
		}

		if (confirmed) {
			if (diMap.containsKey(Character.toUpperCase(c))) {
				final Set<Position> diPositions = diMap.get(Character.toUpperCase(c));
				if (diPositions != null) {
					diPositions.remove(p);
				}
			} else {
				final Set<Position> diPositions = new HashSet<Position>();
				for (int i = 1; i <= degree; i++) {
					if (p.getPosition() != i) {
						diPositions.add(Position.getInstance(i));
					}
				}
				diMap.put(Character.toUpperCase(c), diPositions);
			}
		}
	}

	public boolean hasUniqueNCharacters() {
		// the diMap should never be null
		return /* diMap != null && */diMap.size() == degree;
	}

	private void removeEmptyPositionSets() {

		final Iterator<Entry<Character, Set<Position>>> i = map.entrySet().iterator();

		while (i.hasNext()) {
			final Entry<Character, Set<Position>> entry = i.next();
			final Set<Position> pSet = entry.getValue();
			if (pSet.isEmpty()) {
				i.remove();
				doSet.add(Character.toUpperCase(entry.getKey()));
			}
		}
	}

}
